-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interface Changes for Use in Filtering #56
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
JuliaFormatter
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 231 to 234 in a5a2e05
log_marginal = logpdf( | |
Gaussian(zero(residual), Symmetric(S)), | |
residual | |
) |
[JuliaFormatter] reported by reviewdog 🐶
resample_threshold(filter::BootstrapFilter) = filter.threshold*filter.N |
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 251 to 256 in a5a2e05
function prior( | |
rng::AbstractRNG, | |
model::StateSpaceModel, | |
filter::BootstrapFilter, | |
extra | |
) |
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 258 to 261 in a5a2e05
initial_states = map( | |
x -> rand(rng, init_dist), | |
1:filter.N | |
) |
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 267 to 274 in a5a2e05
rng::AbstractRNG, | |
particles::ParticleContainer, | |
model::StateSpaceModel, | |
filter::BootstrapFilter, | |
step::Integer, | |
extra; | |
debug::Bool = false | |
) |
[JuliaFormatter] reported by reviewdog 🐶
idx = collect(1:filter.N) |
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 290 to 291 in a5a2e05
x -> SSMProblems.simulate(rng, model.dyn, step, x, extra), | |
particles[idx] |
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 298 to 304 in a5a2e05
particles::ParticleContainer, | |
model::StateSpaceModel, | |
observation, | |
filter::BootstrapFilter, | |
step::Integer, | |
extra | |
) |
[JuliaFormatter] reported by reviewdog 🐶
collect(particles) |
[JuliaFormatter] reported by reviewdog 🐶
SSMProblems.jl/examples/particle-mcmc/simple-filters.jl
Lines 312 to 313 in a5a2e05
ParticleContainer(particles.vals, particles.log_weights+log_marginals), | |
logsumexp(log_marginals) - logsumexp(particles.log_weights) |
[JuliaFormatter] reported by reviewdog 🐶
end |
On Type ConsistencyCurrently, the interface contains type parameters for the state and observation objects; while this is ultimately good for forward simulation, we no longer have easy access to the element types. This is used mainly for preallocating particle weights, log evidence, etc and helps avoid unnecessary type conversions. At its worst, this raises errors when facing impossible type conversions. Taking this to an extreme, I also think the user should keep the model element types consistent throughout the dynamics and observation process. This would allow us to employ a type structure like |
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Awesome stuff. I'll take a look through and make some comments. Code style is Blue. I use the formatter baked into the Julia VSCode extension set up to format my code on every save. Works like a charm. |
…SMProblems.jl into ck/particle-methods
A few random thoughts:
I like the look of the "distribution-focused" version of the Kalman Filter. It might be worth leaving a comment clarifying what that is about for Frederic/Hong since I think we spoke more about that by ourselves. I'm apprehensive about using the name |
Particle AncestryI implemented the particle storage algorithm presented in (Murray, 2015). I include a demonstration at the bottom of this file which informally benchmarks the performance of the algorithm. @THargreaves since you brought my attention to this algorithm, your comments would be much appreciated. I was aiming for elegance with this commit, and is thus far from optimal in terms of speed. Feel free to scrap anything remotely wasteful. |
This looks great and closely matches the initial draft that I made a few months back. I've pushed a small change that keeps track of the free indices using a stack. On my computer using the parameters in your test script, this reduced the median time from 48ms to 5ms and I assume will have better performance for sequences with fewer dead branches. I haven't tested it in full generality yet though but intuitively this feels it will be worth it most of the time. Other than that, I think the particle storage code looks great. I'm not sure about the |
@THargreaves legendary commit. 10x speed-up in less than 10 lines of changes.
Yeah, it was purely to demonstrate a proof of concept. Now that you fixed the main drawback of the ancestry, I went ahead and added some key-word arguments to let I also consolidated my demonstrations to script.jl and moved resampling to a separate file. |
examples/particle-mcmc/particles.jl
Outdated
Base.@propagate_inbounds Base.getindex(pc::ParticleContainer, i::Int) = pc.vals[i] | ||
Base.@propagate_inbounds Base.getindex(pc::ParticleContainer, i::Vector{Int}) = pc.vals[i] | ||
|
||
function store!(pc::ParticleContainer, new_states, idx...; kwargs...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't need the parent indices, that's up to the storage implementation to decide how they store ancestry paths:
Maybe something like that is enough ?
""" Store new generation in the container
"""
store!(pc::AbstractParticleContainer, new_generation)
""" Get last generation
"""
load(pc::AbstractParticleContainer)
""" Get (log)-weights from storage
"""
weights(pc::AbstractParticleContainer)
logweights(pc::AbstractParticleContainer)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't need the parent indices, that's up to the storage implementation to decide how they store ancestry paths
I might be misunderstanding you but I think we do, e.g. for smoothing.
If we just pass the particle container multiple vectors of states, it has no idea what the genealogy is so you can't perform naive smoothing on it by back-tracing ancestry.
Maybe something like that is enough ?
I'm a bit unsure on tis too. It feels like the filter is depending a bit too much on the implementation of the storage , which should ideally independent.
My instinct is for the filter to maintain a minimal collection of variables for it to run. I think this generally would just be the current state (represented here as a combination of x values and log weights). It would update these independently of the storage.
Then at each time step, it passes what it currently has to the storage object which can do what it wants with it. The key idea is that the filter should still work even in the extreme case that the storage throws everything away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I might be a bit confused here.
@charlesknipp, what was the intention of ParticleContainer? Is it a type of particle storage (one that just only remembers the current state) or just a representation of the current state?
If the latter, I don't think it and AncestoryContainer should be subtypes of the same type as they do different things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I currently use ParticleContainer
as a means of storage to preserve the weighted nature of the sample at step t
. Although I wonder if we could move ancestry storage to a callback, which would be very elegant if possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay, I'm following now.
I didn't make this clear, but from my point of view store
is the callback.
But rather than defining it as a simple function, it is tied to a storage container.
So the particle filter has a state::ParticleState(xs, log_ws)
(currently called ParticleContainer
but just to make the difference really clear) which it updates either in-place or by replacing with a new ParticleState and then this is passed to store!
after each step to do what it pleases.
And store!
can dispatch on SparseAncestoryStorage <: AbstractParticleStorage <: AbstractStorage
or something like that, which is the Lawrence Murray algorithm you implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, right now store!
is just a means of updating an AbstractStorage
(or AbstractParticleContainer
in my code).
I really like the idea you present with separating particle storage and particle state. Although, that would imply the need to store the ancestry indices in the particle state (which would be necessary for sparse ancestry storage). I'm not 100% sure of the details yet, but I think I can make this look pretty elegant.
A Note on Particle GibbsI updated the filtering code such that reference trajectories can be passed as key word arguments. This allows for conditional SMC to be used within particle Gibbs blocks using the highly efficient sparse ancestry storage. My implementation of CSMC is based mostly on Nicolas Chopin's own particles, and is nowhere near as general as the methods defined in Also relevant to #51, although I'm waiting on the Gibbs extension of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me. Will modify the unit tests now and then should be good to merge.
I guess one thing of note about moving to kwargs is that we no longer have the fine-grained control of which kwargs are accessible at each time step. This actually might be a net positive as it allows for a lot more flexibility and avoids the need for Might be worth thinking if there are any scenarios where this could lead to trouble. I can't imagine there is any computational cost involved. Maybe the closest thing would be that this makes it difficult to use the filter online. We would write def simulate(...; dts, kwargs...)
dt = dts[step]
end Which then expects us to be appending to a vector of mutable struct MemorylessVector{T}
x::T
i::Int
end
Base.getindex(v:: MemorylessVector, i::Int64) = i == v.i ? x : error(..) |
In the added example I propose a structure for generalized filtering, and additionally employ this method within PMMH given a random walk kernel.Each filtering algorithm requires the user to construct 3 functions:1.initialise
for initializing the filtered states (whether it be particles or a Gaussian distribution)2.predict
which resamples the states and performs a one step ahead sampling step3.update
to evaluate the importance weight of the sample and return a marginal log-likelihoodSince migrating the filtering code to
AnalyticalFilters
(see TuringLang/GeneralisedFilters.jl#7), there are minimal changes toSSMProblems
from this PR: